Flutter(dart)でimmutableなクラスを生成するfreezedについてまとめてみた
こんにちは、ゲームソリューション部のsoraです。
今回は、Flutterでimmutableなクラスを生成するfreezedについて書いていきます。
書き方を忘れることが多いため、これを機に自分なりにまとめようと思います。
immutableなクラスとは
immutableなクラスから作成したオブジェクトの値を変更できないクラス
値が変更されないため、同時にアクセスされた場合にも値のずれが起こらない・意図せず値が変更されないなどのメリットがあります。
値を変える必要のある場合以外は、基本的にはimmutableで良さそうかなと思いました。
利用する主要なパッケージ
freezed
Flutter(Dart)でimmutableなクラスを自動生成するパッケージ
json形式の変換も簡単に実装可能
freezed_annotation
freezedのアノテーションを使うためのパッケージ
@freezed
や@JsonSerializable
を使うために必要
build_runner
Dartにてコード生成を行う際に使用するパッケージ
freezedでコードの自動生成を行うために必要
json_annotation
json_serializable
Dartにてjsonの変換を簡単に行ったり、コードを自動生成する際に必要なパッケージ
freezedのクラスにて、jsonを扱う際に必要
pubspec.yaml
上記パッケージを使用するためのpubspec.yaml
を、一部抜粋して以下参考として記載します。
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6
connectivity_plus: ^6.0.3
freezed_annotation: ^2.4.4
json_annotation: ^4.9.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
build_runner: ^2.4.11
freezed: ^2.5.2
json_serializable: ^6.8.0
freezedの使い方
テンプレート
Person(person)の部分をそれぞれ書き換えて利用します。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'person.freezed.dart';
part 'person.g.dart';
class Person with _$Person {
const factory Person({
required String name,
required int age,
}) = _Person;
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}
テンプレートの解説
テンプレートの解説については、以下コメント内に記載しています。
import 'package:freezed_annotation/freezed_annotation.dart';
// freezedで自動生成されるファイル
part 'person.freezed.dart';
part 'person.g.dart';
// freezedでのクラスの作成
// Personクラスの作成、Personクラスをプライベートクラスである_Personクラスに委譲している
class Person with _$Person {
const factory Person({
required String name,
required int age,
}) = _Person;
// json形式の変換
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}
json形式の変換が不要の場合は、part 'person.g.dart';
やjson形式の変換用のコード部分は不要です。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'person.freezed.dart';
class Person with _$Person {
const factory Person({
required String name,
required int age,
}) = _Person;
}
クラスのプロパティについて
freezedクラスのプロパティについて、以下にそれぞれ記載します。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'person.freezed.dart';
part 'person.g.dart';
class Person with _$Person {
// @Asset:条件を満たさない場合に、エラーメッセージを出してインスタンスを生成しない
('name.isNotEmpty', 'Name cannot be empty')
const factory Person({
// required:必須(Nullを許容しない)
required String name,
// ?:Nullを許容
int? age,
// @Default:Nullだった場合のデフォルト値を指定
(00000000000) phoneNumber,
// @JsonKey:jsonのキーとプロパティ名が異なる場合の紐づけ
(name: 'e_mail') required String email,
}) = _Person;
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}
メソッドの追加
freezedクラスの中でメソッドを定義することも可能です。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'person.freezed.dart';
abstract class Person with _$Person {
// カスタムメソッドを追加するためのプライベートコンストラクタの定義
const Person._();
const factory Person({
required String name,
required int age,
}) = _Person;
// カスタムメソッド
void priName() {
print('$name');
}
}
freezedのクラス作成
コードを自動生成する際は、以下コマンドを実行します。
# コード生成
flutter pub run build_runner build
# コード生成(オプションあり)
# ⇒既存の生成ファイルと新しく生成されるファイルが競合した場合に、オプションなしだとエラーが出る場合がある
# ⇒その場合は、オプションを付けることで既存のファイルを削除して生成する
flutter pub run build_runner build --delete-conflicting-outputs
jsonの読み取り
freezedで生成したクラスを利用する方法について、まずリスト化されていないjsonの場合は以下です。
// 非リストの場合
const jsonString = '''
{
"name": "John Doe",
"age": 30,
"phoneNumber": 1234567890,
"e_mail": "john.doe@example.com"
}
''';
Person person = Person.fromJson(json.decode(jsonString));
リスト化されているjsonの場合は以下です。
// リストの場合
const jsonString = '''
[
{
"name": "John Doe",
"age": 30,
"phoneNumber": 1234567890,
"e_mail": "john.doe@example.com"
},
{
"name": "Jane Smith",
"age": 25,
"phoneNumber": 9876543210,
"e_mail": "jane.smith@example.com"
}
]
''';
List<dynamic> jsonData = json.decode(jsonString);
List<Person> person = jsonData.map((data) => Person.fromJson(data)).toList();
サンプルコード
説明は以上ですが、これまでの項目をある程度盛り込んだサンプルコードと実行時の画面も記載しておきます。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'model/person.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp()
)
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.lightBlueAccent),
useMaterial3: true,
fontFamily: 'Noto Sans JP'
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
// // 非リストの場合
// const jsonString = '''
// {
// "name": "John Doe",
// "age": 30,
// "phoneNumber": 1234567890,
// "e_mail": "john.doe@example.com"
// }
// ''';
// Person person = Person.fromJson(json.decode(jsonString));
// リストの場合
const jsonString = '''
[
{
"name": "John Doe",
"age": 30,
"phoneNumber": 2345678901,
"e_mail": "john.doe@example.com"
},
{
"name": "Jane Smith",
"age": 25,
"e_mail": "jane.smith@example.com"
}
]
''';
List<dynamic> jsonData = json.decode(jsonString);
List<Person> person = jsonData.map((data) => Person.fromJson(data)).toList();
final methodTest = person[0].doubleAge();
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('freezedテスト'),
),
body: Center(
child: Column(
children: [
// 非リストの場合
// Text('Name: ${person.name}'),
// Text('Age: ${person.age}'),
// Text('Phone: ${person.phoneNumber}'),
// Text('email: ${person.email}')
// リストの場合
const Text('一人目'),
Text('Name: ${person[0].name}'),
Text('Age: ${person[0].age}'),
Text('Phone: ${person[0].phoneNumber}'),
Text('email: ${person[0].email}'),
Text('Age*2: $methodTest'),
const SizedBox(height: 30,),
const Text('二人目'),
Text('Name: ${person[1].name}'),
Text('Age: ${person[1].age}'),
Text('Phone: ${person[1].phoneNumber}'),
Text('email: ${person[1].email}'),
],
),
),
);
}
}
import 'package:freezed_annotation/freezed_annotation.dart';
part 'person.freezed.dart';
part 'person.g.dart';
abstract class Person with _$Person {
('name.isNotEmpty', 'Name cannot be empty')
const Person._();
const factory Person({
required String name,
required int age,
(01234567890) int? phoneNumber,
(name: 'e_mail') required String email,
}) = _Person;
int doubleAge() {
return age * 2;
}
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}
参考
最後に
今回は、Flutterでimmutableなクラスを生成するfreezedについて記事にしました。
どなたかの参考になると幸いです。